Imports MatrixDataStructures
Imports System.IO

<CLSCompliant(False)> Public Module HdrImgReader   

    Public Enum OutPutType
        int16
        float
        auto
    End Enum

    Public Function ReadFromHdrFile(ByVal fileName As String, Optional ByVal outputType As OutPutType = HdrImgReader.OutPutType.auto) As SingleVolume
        If (Not System.IO.File.Exists(fileName)) Then
            Throw New Exception("File not found.")
        End If

        Dim dimensions As New Coordinate(0, 0, 0)
        Dim fov As New CoordinateD(0, 0, 0)
        Dim origin As New CoordinateD(0, 0, 0)
        Dim bitPix As Short = 0
        Dim dataType As Short = 0

        ParseHeader(fileName, dimensions, fov, origin, dataType, bitPix)
        fov.x *= dimensions.x 'since hdr stores voxel size, not fov
        fov.y *= dimensions.y
        fov.z *= dimensions.z

        If (outputType = HdrImgReader.OutPutType.auto) Then
            If (dataType = 1 Or dataType = 4 Or dataType = 8) Then
                outputType = HdrImgReader.OutPutType.int16
            ElseIf (dataType = 16 Or dataType = 64) Then
                outputType = HdrImgReader.OutPutType.float
            Else
                Throw New Exception("Auto datatype is not assignable. data-type-code: " & dataType)
            End If
        End If

        Dim matrixData As Object = Nothing

        Dim imgPath As String = System.IO.Path.GetDirectoryName(fileName) & "\" & System.IO.Path.GetFileNameWithoutExtension(fileName) & ".img"
        Dim fs As New FileStream(imgPath, FileMode.Open)
        Dim r As New BinaryReader(fs)
        Select Case outputType
            Case HdrImgReader.OutPutType.float
                matrixData = New Matrix3DSingle(dimensions.x, dimensions.y, dimensions.z)
                Select Case dataType
                    Case 16
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DSingle).data(i, dimensions.y - 1 - j, k) = r.ReadSingle()
                                Next
                            Next
                        Next
                    Case 64
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DSingle).data(i, dimensions.y - 1 - j, k) = r.ReadDouble()
                                Next
                            Next
                        Next
                    Case Else
                        Throw New Exception("DataType in img file not handled or not assigned properly. type:tofloat data-type-code:" + dataType.ToString())
                End Select
            Case HdrImgReader.OutPutType.int16
                matrixData = New Matrix3DInt16(dimensions.x, dimensions.y, dimensions.z)
                Select Case dataType
                    Case 1
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DInt16).data(i, dimensions.y - 1 - j, k) = Convert.ToInt16(r.ReadChar())
                                Next
                            Next
                        Next
                    Case 2
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DInt16).data(i, dimensions.y - 1 - j, k) = Convert.ToInt16(r.ReadByte())
                                Next
                            Next
                        Next
                    Case 4
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DInt16).data(i, dimensions.y - 1 - j, k) = r.ReadInt16()
                                Next
                            Next
                        Next
                    Case 8
                        For k As Integer = 0 To dimensions.z - 1
                            For j As Integer = 0 To dimensions.y - 1
                                For i As Integer = 0 To dimensions.x - 1
                                    CType(matrixData, Matrix3DInt16).data(i, dimensions.y - 1 - j, k) = r.ReadInt32()
                                Next
                            Next
                        Next
                    Case Else
                        Throw New Exception("DataType in img file not handled or not assigned properly. type:toint data-type-code:" + dataType.ToString())
                End Select
        End Select

        Dim dataVolume As SingleVolume
        Select Case outputType
            Case HdrImgReader.OutPutType.float
                dataVolume = New SingleVolume(CType(matrixData, Matrix3DSingle), fov, origin)
            Case HdrImgReader.OutPutType.int16
                dataVolume = New SingleVolume(CType(matrixData, Matrix3DInt16), fov, origin)
            Case Else
                dataVolume = Nothing
                Throw New Exception("Datavolume is nothing")
        End Select
        Return dataVolume
    End Function
    Private Sub ParseHeader(ByVal fileName As String, ByRef dimensions As Coordinate, ByRef fov As CoordinateD, ByRef origin As CoordinateD, ByRef dataType As Short, ByRef bitPix As Short)

        Dim fs As FileStream
        Try
            fs = New FileStream(fileName, FileMode.Open)
            Dim r As BinaryReader = New BinaryReader(fs)

            fs.Seek(40, SeekOrigin.Begin) 'skip header

            'read image dimensions
            fs.Seek(2, SeekOrigin.Current) 'skip dim[0]
            dimensions.x = r.ReadInt16()
            dimensions.y = r.ReadInt16()
            dimensions.z = r.ReadInt16()

            'read data-type & bitpix
            fs.Seek(8 + 14, SeekOrigin.Current) 'skip rest of dim[] and unused stuff
            dataType = r.ReadInt16()
            bitPix = r.ReadInt16()

            'read fov
            fs.Seek(6, SeekOrigin.Current)
            fov.x = r.ReadSingle()
            fov.y = r.ReadSingle()
            fov.z = r.ReadSingle()

            fs.Seek(16 + 32 + 8, SeekOrigin.Current) 'skip rest of image-dimensions
            fs.Seek(105, SeekOrigin.Current) 'arrive at originator

            'read origin
            origin.x = r.ReadInt16()
            origin.y = r.ReadInt16()
            origin.z = r.ReadInt16()

            r.Close()
            fs.Close()
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub

End Module

<CLSCompliant(False)> Public Module HdrImgWriter
    Public Enum DataType
        DT_NONE = 0
        DT_UNKNOWN = 0
        DT_BINARY = 1
        DT_UNSIGNED_CHAR = 2
        DT_SIGNED_SHORT = 4
        DT_SIGNED_INT = 8
        DT_FLOAT = 16
        DT_COMPLEX = 32
        DT_DOUBLE = 64
        DT_RGB = 128
        DT_ALL = 255
    End Enum
    Public Sub WriteToFile(ByVal Data As SingleVolume, ByVal fname As String, ByVal type As DataType)

        If (type = DataType.DT_SIGNED_SHORT Or type = DataType.DT_FLOAT) Then
        Else
            MsgBox("Datatype not handled !")
            Exit Sub
        End If

        Dim fs As FileStream
        Dim path As String = System.IO.Path.GetDirectoryName(fname)
        fname = System.IO.Path.GetFileNameWithoutExtension(fname)

        fs = New FileStream(path & "\" & fname & ".hdr", FileMode.Create)

        Dim w As BinaryWriter = New BinaryWriter(fs) 'create reader

        Dim blankShort As Short = 0
        Dim blankSingle As Single = 0
        Dim blankDouble As Double = 0
        Dim blankInteger As Integer = 0
        Dim blankChar As Char = CChar(" ")
        Dim blankString As String = " "

        'read header key
        w.Write(348)

        If (type = DataType.DT_SIGNED_SHORT) Then w.Write("SHORT     ".ToCharArray)
        If (type = DataType.DT_FLOAT) Then w.Write("FLOAT     ".ToCharArray)

        WriteRepeat(w, blankChar, 18)
        w.Write(16384)
        w.Write(blankShort)
        w.Write(CChar("r"))
        w.Write(blankChar)
        w.Write(Convert.ToInt16(3))

        If (type = DataType.DT_SIGNED_SHORT) Then
            w.Write(Convert.ToInt16(Data.Matrix.x))
            w.Write(Convert.ToInt16(Data.Matrix.y))
            w.Write(Convert.ToInt16(Data.Matrix.z))
        ElseIf (type = DataType.DT_FLOAT) Then
            w.Write(Convert.ToInt16(Data.MatrixD.x))
            w.Write(Convert.ToInt16(Data.MatrixD.y))
            w.Write(Convert.ToInt16(Data.MatrixD.z))
        Else
            Exit Sub
        End If

        WriteRepeat(w, blankShort, 11)
        w.Write(Convert.ToInt16(type))
        If (type = DataType.DT_SIGNED_SHORT) Then w.Write(Convert.ToInt16(16))
        If (type = DataType.DT_FLOAT) Then w.Write(Convert.ToInt16(32))

        w.Write(blankShort)              '76
        w.Write(Convert.ToSingle(3))           '80

        If (type = DataType.DT_SIGNED_SHORT) Then
            w.Write(CSng(Data.FOV.x / Data.Matrix.x))
            w.Write(CSng(Data.FOV.y / Data.Matrix.y))
            w.Write(CSng(Data.FOV.z / Data.Matrix.z))
        ElseIf (type = DataType.DT_FLOAT) Then
            w.Write(CSng(Data.FOV.x / Data.MatrixD.x))
            w.Write(CSng(Data.FOV.y / Data.MatrixD.y))
            w.Write(CSng(Data.FOV.z / Data.MatrixD.z))
        End If

        WriteRepeat(w, blankSingle, 12)
        WriteRepeat(w, blankInteger, 2)
        WriteRepeat(w, blankChar, 104)
        w.Write(blankChar)

        w.Write(Convert.ToInt16(Data.Center.x))
        w.Write(Convert.ToInt16(Data.Center.y))
        w.Write(Convert.ToInt16(Data.Center.z))

        WriteRepeat(w, blankShort, 2)
        WriteRepeat(w, blankChar, 53)
        WriteRepeat(w, blankInteger, 8)

        w.Close()
        fs.Close()

        fs = New FileStream(path & "\" & fname & ".img", FileMode.Create)
        w = New BinaryWriter(fs) 'create reader

        If (type = DataType.DT_FLOAT) Then
            If Data.MatrixD IsNot Nothing Then
                For k As Integer = 0 To Data.MatrixD.z - 1
                    For j As Integer = 0 To Data.MatrixD.y - 1
                        For i As Integer = 0 To Data.MatrixD.x - 1
                            w.Write(Data.MatrixD.data(i, Data.MatrixD.y - 1 - j, k))
                        Next
                    Next
                Next
            End If
        End If
        If (type = DataType.DT_SIGNED_SHORT) Then
            If Data.Matrix IsNot Nothing Then
                For k As Integer = 0 To Data.Matrix.z - 1
                    For j As Integer = 0 To Data.Matrix.y - 1
                        For i As Integer = 0 To Data.Matrix.x - 1
                            w.Write(Data.Matrix.data(i, Data.Matrix.y - 1 - j, k))
                        Next
                    Next
                Next
            End If
        End If
        fs.Close()
        w.Close()
    End Sub
    Public Sub WriteToFile(ByVal Data As SingleVolume, ByVal fname As String)
        If (Data.Matrix IsNot Nothing And Data.MatrixD IsNot Nothing) Then
            WriteToFile(Data, fname & "s", DataType.DT_SIGNED_SHORT)
            WriteToFile(Data, fname & "s", DataType.DT_FLOAT)
        Else
            If (Data.Matrix IsNot Nothing) Then
                WriteToFile(Data, fname, DataType.DT_SIGNED_SHORT)
            End If
            If Data.MatrixD IsNot Nothing Then
                WriteToFile(Data, fname, DataType.DT_FLOAT)
            End If
        End If
    End Sub

    Private Sub WriteRepeat(ByRef w As System.IO.BinaryWriter, ByVal o As Object, ByVal n As Integer)
        For i As Integer = 0 To n - 1
            w.Write(o)
        Next
    End Sub
End Module

